package pipeline;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;

import setting.BioNLPSettings;
import util.io.FileUtil;
import abs.syn.DependencyPath;
import abs.syn.Parse;
import def.AbstractText;
import def.ArgType;
import def.Argument;
import def.BioNLPDocument;
import def.Event;
import def.EventType;
import def.Protein;
import def.TSpan;
import def.TextType;
import exp.Count;

public class EdgeDetection {
	public EdgeDetection(){

	}
	public void init(){
		loadPatterns();
	}

	public static void main(String[] args){
		// collecting the patterns

		//		String path = "data/BioNLP09/training/";
		if (args.length == 0){
			System.err.println(" Give the conf file");
			System.exit(-1);

		}
		BioNLPSettings.init(args[0]);
		BioNLPSettings.print();
		if (BioNLPSettings.getValue("mode").equals("learn")){
			//			BioNLPSettings.init("train.conf");
			TriggerDetection td = TriggerDetection.getTD();
			td.init();

			EdgeDetection ed = null;
			if (BioNLPSettings.getValue("EDGE_DETECTOR").equals(BioNLPSettings.EDGE_DICT_DETECTOR)){
				ed = new EdgeDetection();
			}
			else if (BioNLPSettings.getValue("EDGE_DETECTOR").equals(BioNLPSettings.EDGE_SVM_DETECTOR)){
				ed = new EdgeDetectionSVM();
			}
			else{
				System.err.println(BioNLPSettings.getValue("EDGE_DETECTOR")+" not found");
				return;
			}
			ed.td = td;

			System.out.println("mode: learn");
			//			Random r  = new Random(123);
			ed.learn();
		}
		else if (BioNLPSettings.getValue("mode").equals("infer")){

			TriggerDetection td = TriggerDetection.getTD();
			td.init();

			EdgeDetection ed = null;
			if (BioNLPSettings.getValue("EDGE_DETECTOR").equals(BioNLPSettings.EDGE_DICT_DETECTOR)){
				ed = new EdgeDetection();
				ed.init();
			}
			else if (BioNLPSettings.getValue("EDGE_DETECTOR").equals(BioNLPSettings.EDGE_SVM_DETECTOR)){
				ed = new EdgeDetectionSVM();
				ed.init();
			}
			else{
				System.err.println(BioNLPSettings.getValue("EDGE_DETECTOR")+" not found");
				return;
			}
			ed.td = td;

			System.out.println("mode: infer");
			//			BioNLPSettings.EventAnnotationSuffix = ".a2.t1";
			//			BioNLPSettings.parsePath = BioNLPSettings.devParsePath;
			//			BioNLPSettings.parsePath = BioNLPSettings.testParsePath;
			//1. load patterns, keep freq
			//		for (int i = 0; i < 15; i++){
			//			path = "data/BioNLP09/test/";
			//			path = "data/BioNLP09/dev/";
			Count sum = new Count();
			//			sum = new Count();
			String path  =BioNLPSettings.getValue("dataPath");
			long start = System.currentTimeMillis();
//			int[] fileIds = new int[]{10318814,	7587061, 7642615};
//			for (int fileId : fileIds){
			for (String filename :  new File(path).list()){
//							String filename = fileId+".txt";//"9211933.txt";

				if (filename.endsWith(".txt")){
					long start1 = System.currentTimeMillis();
					String filePrefix = filename.replace(".txt", "");
					System.out.println(filename+ " to be processed");
					sum.add(ed.predictDocument(path, filePrefix));
					//				break;
					System.err.println ("[TIME] "+filename+" took "+(System.currentTimeMillis() - start1) / 1000 + " seconds");
				}
			}
			System.err.println ("[TIME] it took "+(System.currentTimeMillis() - start) / 1000 + " seconds");
			System.out.println(sum);
			double d =2/((double)sum.all/sum.num+(double)(sum.num+sum.fp)/sum.num); 
			//				System.err.println("filtering "+i + "  :"+sum+" f1 = "+d);
			System.out.println(" f1 = "+d);
			System.out.println("BE = "+BE+", BP = "+BP);
			System.out.println("DE = "+DE+", DP = "+DP);
			//			filterPatterns(i+1); 
			//		}
		}
	}

	public/* static */ void savePatterns(){
		saveTable(eventThemeEvent, "eventThemeEvent");
		saveTable(eventCauseEvent, "eventCauseEvent");
		saveTable(eventThemeProtein, "eventThemeProtein");
		saveTable(eventCauseProtein, "eventCauseProtein");
	}

	private static void saveTable(Hashtable<String, Integer> table, String filename){
		StringBuilder sb = new StringBuilder();
		System.err.println("**********"+filename+"**********");
		int[] freq = new int[10];
		for (Entry<String, Integer> e: table.entrySet()){
			System.err.println(e.getKey()+"\t"+e.getValue());
			if (e.getKey().startsWith("H")){
				String[] items = e.getKey().split("(\\+|\\-)");
				if (items.length < 10)
					freq[items.length] += e.getValue();
			}
			sb.append(e.getKey()+"\t"+e.getValue()+"\n");
		}
		System.err.println("**********Frequency**********");
		for (int i = 0; i < freq.length; i++){
			System.err.println("length "+i + " = "+freq[i]);
		}
		FileUtil.writeTextToFile(sb.toString(), filename);
	}

	public /*static*/ void loadPatterns(){
		loadTable(eventThemeEvent, "eventThemeEvent");
		loadTable(eventCauseEvent, "eventCauseEvent");
		loadTable(eventThemeProtein, "eventThemeProtein");
		loadTable(eventCauseProtein, "eventCauseProtein");
	}

	private static void loadTable(Hashtable<String, Integer> table, String filename) {
		String[] lines = FileUtil.getTextFromFile(filename).split("\n");
		for (String line: lines){
			String[] pair = line.split("\t");
			if (Integer.parseInt(pair[1])> BioNLPSettings.getIntValue("patternFreq")){
				if (BioNLPSettings.getBooleanValue("USE_DEP_SIGN"))
					table.put(pair[0], Integer.parseInt(pair[1]));
				else
					table.put(pair[0].replace("+","").replace("-", ""), Integer.parseInt(pair[1]));
			}
		}
	}

	public /*static*/ void filterPatterns(int i ){
		filterTable(eventThemeEvent, i);
		filterTable( eventCauseEvent, i);
		filterTable(eventThemeProtein, i);
		filterTable(eventCauseProtein, i);
	}

	private static void filterTable(Hashtable<String, Integer> table, int i) {
		String[] iter = table.keySet().toArray(new String[0]);
		for (String it: iter){
			if (table.get(it) <= i)
				table.remove(it);
		}
	}

	public /*static*/ Count predictDocument(String path, String filePrefix) {
		Count c = new Count();
		BioNLPDocument doc = new BioNLPDocument(path, filePrefix);
		if (doc.tspans.size() == 0){
			System.err.println(filePrefix + " skipped |tspan| = 0");
			return c;
		}
		if (!doc.valid){
			System.err.println(filePrefix + " skipped : doc reading invalid");
			return c;
		}

		//****DEBUG****
		//		debugGraph(doc);
		//*****DEBUG END******
		//		c.all++;


		// 2. call <code>TriggerDetection</code> to find triggers
		//		ArrayList<TSpan> triggers = td.detectTriggersSVM(doc);
		ArrayList<TSpan> triggers = td.detectTriggers(doc);
		System.err.println("*********[TRIGGER]"+doc.fileId+"**********");
		for(TSpan ts: triggers){
			System.err.println("[TRIGGER] "+ts);
		}
		//		SolutionEnumeration.enumerate(doc, 0, triggers);
		//		System.err.println("[ENUMERATION] sentence 0 finished" );
		//		SolutionEnumeration.enumerate(doc, 1, triggers);
		//		System.err.println("[ENUMERATION] sentence 1 finished" );
		//		SolutionEnumeration.enumerate(doc, 2, triggers);
		//		System.err.println("[ENUMERATION] sentence 2 finished" );
		//		
		//		SolutionEnumeration.enumerate(doc, 3, triggers);
		//		System.err.println("[ENUMERATION] sentence 3 finished" );
		//		
		//		SolutionEnumeration.enumerate(doc, 4, triggers);
		//		System.err.println("[ENUMERATION] sentence 4 finished" );

		Hashtable<String, Protein> corefProteins = getCorefProteins(doc);

		// building events
		Hashtable<TSpan, Event> predEvents = build(doc, triggers, corefProteins);

		// post-processing, delete events with a null event as theme
		// if event has a protein as theme, delete other event themes
		HashSet<Event> finalEvents = postprocess(doc, predEvents);

		// eval
		//		eval(new HashSet<Event>(predEvents.values()), doc, c);

		// convert to BioNLP format
		FinalPrediction.saveToBioNLPFormat(doc, new ArrayList<Event>(finalEvents), 
				BioNLPSettings.getValue("PRED_DIR")+doc.fileId+BioNLPSettings.getValue("EventAnnotationSuffix"), corefProteins);

		return c;
	}

	public Hashtable<String, Protein> getCorefProteins(BioNLPDocument doc){
		ArrayList<Protein> proteins = new ArrayList<Protein>(doc.proteins);
		Collections.sort(proteins, new Comparator<Protein>(){
			@Override
			public int compare(Protein arg0, Protein arg1) {
				return arg0.tspan.startIdx - arg1.tspan.startIdx;
			}
		});

		Hashtable<String, Protein> corefProteins = new Hashtable<String,Protein>();
		if (proteins.size()>1){
			for (int i = 0; i < proteins.size() -1; i++){
				String dist = doc.text.text.substring(proteins.get(i).tspan.endIdx, proteins.get(i+1).tspan.startIdx);
				if (/*proteins.get(i).tspan.endIdx+4 >= proteins.get(i+1).tspan.startIdx
						&&*/ (dist.replace("[corrected]", "").trim().equals("(")
								&& doc.text.text.charAt(proteins.get(i+1).tspan.endIdx) == ')') || dist.matches("\\s+")){
					if (proteins.get(i).tspan.endIdx-proteins.get(i).tspan.startIdx > 
					proteins.get(i+1).tspan.endIdx - proteins.get(i+1).tspan.startIdx){
						corefProteins.put(proteins.get(i).tspan.tid, proteins.get(i+1));
					}
					else{
						corefProteins.put(proteins.get(i+1).tspan.tid, proteins.get(i+1));
					}
					i++;
				}
			}
		}
		System.err.println("[COREF_PROTEINS] from "+proteins);
		System.err.println("[COREF_PROTEINS] found "+corefProteins);
		return corefProteins;
	}


	/**
	 * given detected triggers, find all possible edges
	 * @param doc
	 * @param triggers
	 * @param corefProteins 
	 * @return
	 */
	public /*static*/ Hashtable<TSpan, Event>  build(BioNLPDocument doc, ArrayList<TSpan> triggers, Hashtable<String, Protein> corefProteins){
		// 3. multiple? passes, in pipeline

		//		Collection<Argument> proteins = doc.tspans.values();
		ArrayList<Protein> proteins = new ArrayList<Protein>(doc.proteins);

		// remove "PROTEIN (PROTEIN)"
		for (Protein p : corefProteins.values()){
			proteins.remove(p);
		}


		Hashtable<TSpan, Event> predEvents = new Hashtable<TSpan, Event>();
		System.err.println("*********[EDGE]"+doc.fileId+"**********");
		int rec = 0, all = 0;
		// recall with completed jobs
		int comprec = 0, compall = 0;
		for (int i =0 ; i < doc.aText.lines.length; i++){
			HashSet<Event> curEvents = new HashSet<Event>();
			System.err.println("====LINE: "+doc.aText.lines[i]+"========");
			// 3a. for each line of text
			// 3b. for each pair of trigger and protein, if it fits a pattern, add it in.
			Parse parse = doc.parses[i];
			int numTrigger =  0, numProtein = 0;
			int regNum = 0;

			for (TSpan trigger: triggers){
				// restrict to the current line
				if (doc.aText.getSentenceIdByPos(trigger.startIdx)!=i) 
					continue;
				if (td.parentDictionary.contains(trigger.text.toLowerCase())){
					regNum++;
				}
				Event e = null;
				numTrigger ++;
				// could be protein
				for (Argument protein: proteins){
					// restrict to the current line
					if (doc.aText.getSentenceIdByPos(protein.tspan.startIdx)!=i) 
						continue;
					numProtein++;
					// find (shortest) dep path or hyphen
					DependencyPath pp = 
						parse.getShortestPath(trigger.startIdx-doc.aText.sentStartPos[i], trigger.endIdx-doc.aText.sentStartPos[i], 
								protein.tspan.startIdx-doc.aText.sentStartPos[i], protein.tspan.endIdx-doc.aText.sentStartPos[i]);

					if (pp!=null){
						String p = pp.getFeatureString();
						System.err.println(trigger.text+"; "+protein.tspan.text+", trigger-protein found path:"+p);
						// if dp fits in eventTHemeProtein / eventCauseProtein
						// build an event based on this tspan, add the protein to t/c
						if (eventThemeProtein.containsKey(p) || eventCauseProtein.containsKey(p)){
							//							if (pp.deps.size() <= 4){
							if (e==null){
								if ( !predEvents.containsKey(trigger)){
									e= new Event();
									e.tspan = trigger;
									predEvents.put(trigger, e);
									curEvents.add(e);
								}
								else{
									e = predEvents.get(trigger);
								}
							}
							e.args.add(protein);
							if (eventThemeProtein.containsKey(p)){
								int score = eventThemeProtein.get(p);
								if (eventCauseProtein.containsKey(p)&&eventCauseProtein.get(p)>score && eventCauseProtein.get(p) >= 4){
									e.types.add(ArgType.Cause);
								}
								else{
									e.types.add(ArgType.Theme);
								}
							}
							else{
								e.types.add(ArgType.Cause);
							}
						}
					}
					else{
						System.err.println(trigger.text+"; "+protein.tspan.text+", trigger-protein not found path:");
					}
				}
				// *** if not in regulation dictionary, then continue *** 
				if (!td.parentDictionary.contains(trigger.text.toLowerCase()))
					continue;

				// or could be another trigger
				for (TSpan anotherTrigger: triggers){
					if (anotherTrigger == trigger) 
						continue;
					// restrict to the current line
					if (doc.aText.getSentenceIdByPos(anotherTrigger.startIdx)!=i) continue;
					// find (shortest) dep path or hyphen
					DependencyPath pp = 
						parse.getShortestPath(trigger.startIdx-doc.aText.sentStartPos[i], trigger.endIdx-doc.aText.sentStartPos[i], 
								anotherTrigger.startIdx-doc.aText.sentStartPos[i], anotherTrigger.endIdx-doc.aText.sentStartPos[i]);

					if (pp!=null){
						String p =  pp.getFeatureString();
						System.err.println(trigger.text+"; "+anotherTrigger.text+", trigger-trigger found path: "+p);
						// if dp fits in eventTHemeEvent
						// build an event based on this tspan, add the protein to t/c
						if (eventThemeEvent.containsKey(p) || eventCauseEvent.containsKey(p)){
							//						if (pp.deps.size() <= 5){
							if (e==null){
								if ( !predEvents.containsKey(trigger)){
									e= new Event();
									e.tspan = trigger;
									predEvents.put(trigger, e);
									curEvents.add(e);
								}
								else{
									e = predEvents.get(trigger);
								}
							}
							Event anotherEvent = null;
							if (!predEvents.containsKey(anotherTrigger)){
								anotherEvent =  new Event();
								anotherEvent.tspan = anotherTrigger;
								predEvents.put(anotherTrigger, anotherEvent);
								curEvents.add(anotherEvent);
							}
							else{
								anotherEvent = predEvents.get(anotherTrigger);
							}
							// check if adding will cause loop
							// FIXME the order of adding events matters 
							if (!anotherEvent.hasChild( e)){
								e.args.add(anotherEvent);
								if (eventThemeEvent.containsKey(p)){
									if (eventCauseEvent.containsKey(p)&& eventThemeEvent.get(p) < eventCauseEvent.get(p)){
										e.types.add(ArgType.Cause);
									}
									else
										e.types.add(ArgType.Theme);
								}
								else if (eventCauseEvent.containsKey(p)){
									e.types.add(ArgType.Cause);
								}
							}
						}
					}
					else{
						System.err.println(trigger.text+"; "+anotherTrigger.text+", trigger-trigger not found path:");
					}
				}
			}
			if (numProtein!=0)
				System.err.println("[STAT]\t"+numTrigger+"\t"+numProtein+"\t"+regNum);
			if (numProtein> 600){
				System.err.println("[TEXT] "+doc.fileId+" \t"+doc.aText.lines[i]);
			}
			/*
			// enumerations  
			SolutionEnumeration2 se = new SolutionEnumeration2();
			se.enumerate(this, doc, i, curEvents);
			System.err.println("[DEBUGCOUNTER]"+se.debugCounter);
			rec += se.maxFoundNum;
			all += se.all;
			if (se.debugCounter < 1e7){
				comprec += se.maxFoundNum;
				compall += se.all;
				System.err.println("[SENT_EVENT_COVER]"+doc.fileId+"@"+i+"\t"+se.maxFoundNum+" / "+se.all  + " completed");
			}
			else{
//			Count c = new Count();
//			eval(curEvents, doc, i, c);
				System.err.println("[SENT_EVENT_COVER]"+doc.fileId+"@"+i+"\t"+se.maxFoundNum+" / "+se.all );
			}*/
		}
		// enumerations
		//		System.err.println("[DOC_EVENT_COVER]"+doc.fileId+"\t"+rec+" / "+all);
		//		System.err.println("[DOC_EVENT_COVER_COMPLETE]"+doc.fileId+"\t"+comprec+" / "+compall);
		return predEvents;
	}


	/**
	 * after <code>build</code>, post-processing for splitting and duplicating events
	 * also, assign event classes
	 * @param predEvents
	 * @return
	 */
	public HashSet<Event> postprocess(BioNLPDocument doc, Hashtable<TSpan, Event> predEvents) {
		// specially treat "overexpress" and "transfect"
		Hashtable<TSpan, Event> addon = new Hashtable<TSpan, Event>();
		for (TSpan key: predEvents.keySet()){
			if (key.text.toLowerCase().contains("overexpress")|| key.text.toLowerCase().contains("transfect")){
				// make another tspan ts2 as positive regulation with this ts as argument
				// this argument is always expression
				TSpan ts2 = key.clone();
				ts2.tid = null;
				ts2.textType = TextType.Positive_regulation;
				Event e= new Event();
				e.tspan = ts2;
				e.args.add(predEvents.get(key));
				e.types.add(ArgType.Theme);
				addon.put(ts2, e);

				key.textType = TextType.Gene_expression;
				predEvents.get(key).eventType = EventType.Gene_expression;

				// find out if any other event taking "key" event as argument
				Event child = predEvents.get(key);
				for (TSpan t: predEvents.keySet()){
					if (t == key) continue;
					Event e2 = predEvents.get(t);
					for (int i = 0; i < e2.args.size(); i++){
						if (e2.args.get(i) ==  child){
							System.err.println("[POSTPROCESS] replacing {"+child+"} with {"+e+"}");
							e2.args.set(i, e);
						}
					}
				}
			}
		}
		predEvents.putAll(addon);



		// 3c. TODO if we have more than 2 proteins, and several of them are in conj_dep, split the events(cf. uturku system)
		// OBSELETE: 3d. after the first step, for each pair triggers, if fits...

		// TODO FIXME if event has already assigned a class, don't change it!! verify the conditions, e.g. removing causes.

		HashSet<Event> finalEvents = new HashSet<Event>();
		Hashtable<Event, Boolean> visited = new Hashtable<Event, Boolean>();
		System.err.println("check=====");
		for (Event e: predEvents.values()){
			System.err.println("new check");
			checkValid(e, new HashSet<Event>(), visited);
		}
		System.err.println("check===done==");
		boolean changed = false;
		Hashtable<Event, ArrayList<Event>> splitted= new Hashtable<Event, ArrayList<Event>>();
		do{
			System.err.println("splitting again");
			changed = false;
			//			visited.clear();
			for (Event e: predEvents.values()){
				boolean skip = false;
				for (Entry<Event, Boolean> entry: visited.entrySet() ){
					if (entry.getKey() == e){
						if (! entry.getValue()) {skip = true;}
						break;
					}
				}
				//				System.err.println("skip "+skip);
				if (skip) continue;
				if (e.args.size() >1 ){
					boolean split = false;
					for (Event ee: splitted.keySet()){
						if (ee == e){
							split = true;
							break;
						}
					}

					if (split){
						System.err.println("already split >1 "+ split);
						// check if its arguments have splittings						
						for (int i = 0; i < e.args.size(); i++){
							for (Event ee: splitted.keySet()){
								if (ee == e.args.get(i)){
									// found; further split 
									System.err.println("[FURTHER_SPLIT] "+e+"  AND  "+ee);
									ArrayList<Event> tempEvents = new ArrayList<Event>();
									// get the splitted event
									ArrayList<Event> argEvents = splitted.get(ee);
									if (argEvents.size() <= 1){
										break;
									}
									// get the incompletely splitted events 
									ArrayList<Event> unsplitEvents = splitted.get(e);
									System.err.println("[FURTHER_SPLIT] to be split further: "+unsplitEvents);
									// for each, find the argument, further split
									for (Event temp: unsplitEvents){
										boolean furtherSplit = false;
										for (Argument arg: temp.args){
											if (arg == ee){
												// the argument found; duplicate the events
												for (Event argEvent: argEvents){
													Event tempEvent = new Event();
													tempEvent.tspan = temp.tspan;
													tempEvent.eventType = e.eventType;
													for (int j = 0; j < temp.args.size(); j++){
														if (temp.args.get(j) == arg){
															System.err.println("[FURTHER_SPLIT] replacing with "+argEvent);
															tempEvent.args.add(argEvent);
															tempEvent.types.add(temp.types.get(j));
														}
														else{
															tempEvent.args.add(temp.args.get(j));
															tempEvent.types.add(temp.types.get(j));
														}
													}
													tempEvents.add(tempEvent);

												}
												furtherSplit = true;
												break;
											}
										}
										if (!furtherSplit){
											tempEvents.add(temp);
										}
									}
									splitted.put(e, tempEvents);
									System.err.println("[FURTHER_SPLIT] results = "+splitted.get(e));
									// FIXME!!
									//									changed = true;
									break;
								}
							}
						}
					}
					else{
						System.err.println("haven't splitted yet "+ split);
						ArrayList<Event> tempEvents = new ArrayList<Event>();

						if (td.bindingDictionary.contains(e.tspan.text)){
							tempEvents = splitBinding2(e,doc);
						}
						else{
							boolean isRegulate = td.parentDictionary.contains(e.tspan.text);
							ArrayList<Integer> causes = new ArrayList<Integer>();
							for (int i = 0; i < e.args.size(); i++){
								if (e.types.get(i) ==  ArgType.Cause){
									causes.add(i);
								}
							}
							for (int i = 0; i < e.args.size(); i++){
								if (causes.contains(i)) 
									continue;
								//								if (false)
								{
									Event tempEvent = new Event();
									tempEvent.eventType = e.eventType;
									tempEvent.tspan = e.tspan;
									tempEvent.args.add(e.args.get(i));
									tempEvent.types.add(e.types.get(i));
									tempEvents.add(tempEvent);
								}
								if (isRegulate && !causes.isEmpty()){
									//									if (false)
									{
										for (Integer j: causes){
											Event tempEvent = new Event();
											tempEvent.eventType = e.eventType;
											tempEvent.tspan = e.tspan;
											tempEvent.args.add(e.args.get(i));
											tempEvent.types.add(e.types.get(i));
											tempEvent.args.add(e.args.get(j));
											tempEvent.types.add(e.types.get(j));
											tempEvents.add(tempEvent);
										}
									}
								}
							}
						}
						splitted.put(e, tempEvents);
						changed = true;
					}
				}
				else if (e.args.size() == 1){
					boolean selfsplit = false;
					for (Event ee: splitted.keySet()){
						if (ee == e){
							selfsplit = true;
							break;
						}
					}
					if (selfsplit) continue;
					System.err.println("already split >1 "+ selfsplit);					
					ArrayList<Event> split = null;

					for (Entry<Event, ArrayList<Event>> ee: splitted.entrySet()){
						if (ee.getKey() == e.args.get(0)){
							split = ee.getValue();
							break;
						}
					}
					//					System.err.println("already split "+ split==null);

					if (split!=null){
						ArrayList<Event> tempEvents = new ArrayList<Event>();
						for (int i = 0; i < split.size(); i++){
							Event argEvent = split.get(i);
							Event tempEvent = new Event();
							tempEvent.eventType = e.eventType;
							tempEvent.tspan = e.tspan;
							tempEvent.args.add(argEvent);
							tempEvent.types.add(e.types.get(0));
							tempEvents.add(tempEvent);
						}
						splitted.put(e, tempEvents);
						changed = true;
					}
				}
			}
		}		while (changed);

		//		System.err.println("split done");
		for (Event e: predEvents.values()){
			boolean skip = false;
			for (Entry<Event, Boolean> entry: visited.entrySet() ){
				if (entry.getKey() == e){
					if ( !entry.getValue()) {
						skip = true;
					}
					break;
				}
			}

			if (skip) continue;
			if (e.args.size()== 0) continue;
			boolean split = false;
			for (Entry<Event, ArrayList<Event>> entry:splitted.entrySet()){
				if (entry.getKey() == e){
					split = true;
					for (Event e0: entry.getValue()){
						finalEvents.add(e0);
					}
					break;
				}
			}
			if (!split){
				finalEvents.add(e);
			}
		}
		System.err.println("=======================Predictions:=========================");
		for (Event e: predEvents.values()){
			System.err.println(" ***** "+e);
		}
		System.err.println("=========================Predictions after post-processing:=========================");
		for (Event e: finalEvents){
			System.err.println(" ***** "+e);
		}

		assignEventClass(doc, finalEvents);
		System.err.println("=========================Predictions after assigning event classes:=========================");
		for (Event e: finalEvents){
			System.err.println(" ***** "+e);
		}

		return finalEvents;
	}

	/**
	 * intelligent split by grouping the first dep
	 * @param e
	 * @return
	 */
	private ArrayList<Event> splitBinding2(Event e, BioNLPDocument doc) {
		ArrayList<Event> tempEvents = new ArrayList<Event>();
		Hashtable<String, ArrayList<Argument>> groups = new Hashtable<String, ArrayList<Argument>>();
		Parse parse = doc.parses[doc.getSentenceIdByPos(e.tspan.startIdx)];
		int offset = doc.aText.sentStartPos[doc.getSentenceIdByPos(e.tspan.startIdx)];
		for (int i = 0; i < e.args.size(); i++){
			// skip if arg is event
			if (e.types.get(i) ==  ArgType.Cause || e.args.get(i) instanceof Event)
				continue;
			DependencyPath dp = parse.getShortestPath(e.tspan.startIdx - offset, e.tspan.endIdx- offset,
					e.args.get(i).tspan.startIdx- offset, e.args.get(i).tspan.endIdx- offset);
			if (dp!=null){
				if (!groups.containsKey(dp.deps.get(0).name))
					groups.put(dp.deps.get(0).name, new ArrayList<Argument>());
				groups.get(dp.deps.get(0).name).add(e.args.get(i));	
			}
			else{
				if (!groups.containsKey("null"))
					groups.put("null", new ArrayList<Argument>());
				groups.get("null").add(e.args.get(i));
			}
		}
		if (groups.size() == 1){
			// FIXME
			for (String key: groups.keySet()){
				Collections.sort(groups.get(key), new Comparator<Argument>(){
					@Override
					public int compare(Argument o1, Argument o2) {
						return o1.tspan.startIdx - o2.tspan.startIdx;
					}
				});

				ArrayList<Argument > args = groups.get(key);

				if (key.equals("prep_between")){
					// combine the arguments

					if (groups.get(key).size() > 3){
						System.err.println("[BINDING_SPLIT] "+doc.fileId+" prep_between for more than 3 proteins ["+groups.get(key)+"]");
					} 
					Event tempEvent = new Event();
					tempEvent.tspan = e.tspan;
					tempEvent.eventType = e.eventType;
					for (int i = 0; i < groups.get(key).size(); i++){
						tempEvent.args.add(groups.get(key).get(i));
						tempEvent.types.add(ArgType.valueOf((i==0?"Theme":("Theme"+(i+1)))));
					}
					tempEvents.add(tempEvent);
				}
				else if (e.tspan.text.startsWith("interaction")&&args.size() == 2 
						&& doc.text.text.substring(args.get(0).tspan.endIdx, args.get(1).tspan.startIdx).matches("(\\-|/)")) {
					Event tempEvent = new Event();
					tempEvent.tspan = e.tspan;
					tempEvent.eventType = e.eventType;
					for (int i = 0; i < args.size(); i++){
						tempEvent.args.add(args.get(i));
						tempEvent.types.add(ArgType.valueOf((i==0?"Theme":("Theme"+(i+1)))));
					}
					tempEvents.add(tempEvent);
				}
				else{
					// split into single ones
					for (Argument arg: groups.get(key)){
						Event tempEvent = new Event();
						tempEvent.eventType = e.eventType;
						tempEvent.tspan = e.tspan;
						tempEvent.args.add(arg);
						tempEvent.types.add(ArgType.Theme);
						tempEvents.add(tempEvent);
					}
				}
			}
		}
		else if (groups.size() == 2){
			String[] keys = groups.keySet().toArray(new String[0]);
			for (Argument arg1 : groups.get(keys[0])){
				for (Argument arg2: groups.get(keys[1])){
					Event tempEvent = new Event();
					tempEvent.tspan = e.tspan;
					tempEvent.eventType = e.eventType;
					if (arg1.tspan.startIdx < arg2.tspan.startIdx){
						tempEvent.args.add(arg1);
						tempEvent.types.add(ArgType.Theme);
						tempEvent.args.add(arg2);
						tempEvent.types.add(ArgType.Theme2);
					}
					else{
						tempEvent.args.add(arg2);
						tempEvent.types.add(ArgType.Theme);
						tempEvent.args.add(arg1);
						tempEvent.types.add(ArgType.Theme2);
					}
					tempEvents.add(tempEvent);
				}
			}
		}
		else{
			System.err.println("[BINDING_SPLIT] "+doc.fileId+" has three groups for "+ e +" WITH "+groups.keySet());
		}
		return tempEvents;
	}
	/**
	 *  agnostic split
	 * @param e
	 * @return
	 */
	private ArrayList<Event> splitBinding(Event e) {
		ArrayList<Event> tempEvents = new ArrayList<Event>();
		for (int i = 0; i < e.args.size(); i++){
			// skip if arg is event
			if (e.types.get(i) ==  ArgType.Cause || e.args.get(i) instanceof Event)
				continue;
			// single ones 
			{
				Event tempEvent = new Event();
				tempEvent.tspan = e.tspan;

				tempEvent.args.add(e.args.get(i));
				tempEvent.types.add(ArgType.Theme);
				tempEvents.add(tempEvent);
			}

			// pairs
			for (int j = i+1 ; j < e.args.size(); j++){
				if (e.types.get(j) ==  ArgType.Cause || e.args.get(j) instanceof Event ){
					continue;
				}
				{
					Event tempEvent = new Event();
					tempEvent.tspan = e.tspan;
					if (e.args.get(i).tspan.startIdx < e.args.get(j).tspan.startIdx){
						tempEvent.args.add(e.args.get(i));
						tempEvent.types.add(ArgType.Theme);
						tempEvent.args.add(e.args.get(j));
						tempEvent.types.add(ArgType.Theme2);
					}
					else{
						tempEvent.args.add(e.args.get(j));
						tempEvent.types.add(ArgType.Theme);
						tempEvent.args.add(e.args.get(i));
						tempEvent.types.add(ArgType.Theme2);
					}
					tempEvents.add(tempEvent);
				}
			}
		}
		return tempEvents;
	}

	/** 
	 * assign event classes
	 * @param finalEvents
	 */
	public void assignEventClass(BioNLPDocument doc, HashSet<Event> finalEvents){
		// get rid of events sharing single proteins
		Hashtable<Argument, List<Event>> sharingEvents = new Hashtable<Argument, List<Event>>();
		for (Event e: finalEvents){
			if (e.args.size() == 1){
				if (e.args.get(0) instanceof Protein){
					if (!sharingEvents.containsKey(e.args.get(0))){
						sharingEvents.put(e.args.get(0), new ArrayList<Event>());
					}
					sharingEvents.get(e.args.get(0)).add(e);
				}
			}
		}
		for (Argument arg: sharingEvents.keySet()){
			List<Event> list = sharingEvents.get(arg);
			if (list.size() <= 1){
				continue;
			}
			int min = 100;
			Event event = null; 
			for (Event e: list){
				int c = 0;
				int sid = doc.getSentenceIdByPos(e.tspan.startIdx);
				Parse parse = doc.parses[sid];
				c = parse.getShortestPath(e.tspan.startIdx - doc.aText.sentStartPos[sid], e.tspan.endIdx - doc.aText.sentStartPos[sid],
						arg.tspan.startIdx - doc.aText.sentStartPos[sid], arg.tspan.endIdx - doc.aText.sentStartPos[sid]).deps.size();
				//				for (String type: td.freq.keySet()){
				//					if (td.freq.get(type).containsKey(e.tspan.text)){
				//						c++;
				//					}
				//				}
				if (c < min){
					min = c;
					event = e;
				}
			}
			for (Event e: list){
				if (event== e)
					continue;
				finalEvents.remove(e);
				System.err.println("[REMOVING] "+e);
			}
		}

		// assign event class
		for (Event e: finalEvents){
			if (e.eventType!=null)
				continue;
			boolean regEvent = false;
			for (int i = 0; i < e.args.size(); i++){
				if (e.args.get(i) instanceof Event || !e.types.get(i).match(ArgType.Theme)){
					regEvent = true;
					break;
				}

			}
			EventType et = null;
			double max = 0;

			if (!regEvent && e.args.size()>1){
				et = EventType.Binding;
			}
			else{
				if (regEvent && !td.parentDictionary.contains(e.tspan.text.toLowerCase()))
				{

				}
				else{
					String str = e.tspan.text.toLowerCase();
					if (str.contains(" ")){
						// multiple words
						String[] items = str.split("\\s+");
						for (String item: items){
							for (String key:td.freq.keySet()){
								if (regEvent&&!key.contains("egulation"))
									continue;
								if (td.freq.get(key).containsKey(item)){
									double v = td.freq.get(key).get(item);
									if (v > max){
										et = EventType.valueOf(key);
										max = v;
									}
								}
							}
						}
					}
					else{
						for (String key:td.freq.keySet()){
							if (regEvent&&!key.contains("egulation"))
								continue;
							if (td.freq.get(key).containsKey(str)){
								double v = td.freq.get(key).get(str);
								if (v > max){
									et = EventType.valueOf(key);
									max = v;
								}
							}
						}
					}
				}
				if (et ==null){
					if (regEvent){
						et = EventType.Regulation;
					}
					else if (e.args.size() > 1){
						et = EventType.Binding;
					}
					else{
						//					et = EventType.Transcription;
					}
				}
			}
			//			if (BioNLPSettings.getValue("TRIGGER_DETECTOR").equals(BioNLPSettings.TRIGGER_ORACLE_DETECTOR)){
			//				e.eventType = EventType.valueOf(e.tspan.textType.toString());
			//			}
			//			else
			{
				e.eventType = et;
				if (et != null){
					TextType tt = TextType.valueOf(et.name());
					if (e.tspan.textType !=null && e.tspan.textType != tt){
						TSpan tspan = e.tspan.clone();
						e.tspan = tspan;
					}
					e.tspan.textType = tt;
				}
			}
		}
	}

	private void debugGraph(BioNLPDocument doc){
		//FIND OUT how the graph of events usually is
		Hashtable<Argument, Event> beChild = new Hashtable<Argument, Event>();
		boolean shareArg = false;
		int numEdge = 0;
		Hashtable<TSpan, Integer> bp = new Hashtable<TSpan, Integer>(), be = new Hashtable<TSpan, Integer>();
		for (Event e: doc.events.values()){
			//			System.err.println("[EVENT-BE]"+e.eid+"  "+be);
			//			System.err.println("[EVENT-BP]"+e.eid+"  "+bp);
			boolean onlyHaveProteins = true;
			for(Argument arg: e.args){
				if (arg instanceof Event){
					onlyHaveProteins = false;
					numEdge ++;
					for (Entry<Argument, Event> entry : beChild.entrySet()){
						if (entry.getKey() == arg){

							System.err.println("[GRAPHSTAT]: shared Argument "+arg+" by "+ e +"    @@@@   "+entry.getKey() +" by "+entry.getValue());
							if (e.tspan.equals(entry.getValue().tspan))
								shareArg = true;
							break;
						}

					}
					beChild.put(arg,e );
				}
				else if (arg instanceof Protein){
					numEdge++;
				}
			}
			if (onlyHaveProteins){
				if (!bp.containsKey(e.tspan))
					bp.put(e.tspan, 0);
				bp.put(e.tspan, bp.get(e.tspan)+1);
			}
			else{
				if (!be.containsKey(e.tspan)) 
					be.put(e.tspan, 0);
				be.put(e.tspan, be.get(e.tspan)+1);
			}
		}
		//		System.err.println("[BP]"+bp);
		//		System.err.println("[BE]"+be);

		for (TSpan ts: bp.keySet()){
			if (bp.get(ts) > BP)
				BP = bp.get(ts);
			if (bp.get(ts) > 6)
				System.err.println("[BP]"+doc.fileId+"   "+ts);
		}
		for (TSpan ts: be.keySet()){
			if (be.get(ts) > BE)
				BE = be.get(ts);
			if (be.get(ts) > 6)
				System.err.println("[BE]"+doc.fileId+"   "+ts);
		}

		AbstractText text =  (AbstractText)doc.text;

		ArrayList<TSpan> triggers = td.detectTriggers(doc);
		for (int i = 0; i <text.lines.length; i++){
			int numPar = 0, numChild = 0, numBinding = 0, numProtein = 0, numArgs = 0, numEvents = 0;

			for (TSpan ts: triggers){
				// restrict to the current line
				if (text.getSentenceIdByPos(ts.startIdx)!=i) continue;

				if (td.parentDictionary.contains(ts.text.toLowerCase())){
					numPar++;
				}
				else{
					if (td.bindingDictionary.contains(ts.text.toLowerCase())){
						numBinding++;
					}
					else{
						numChild++;
					}
				}
			}
			for (Protein protein: doc.proteins){
				if (text.getSentenceIdByPos(protein.tspan.startIdx)!=i) continue;
				numProtein++;
			}

			for (Event e: doc.events.values()){
				if (text.getSentenceIdByPos(e.tspan.startIdx) == i){
					numArgs+=e.args.size();
					numEvents +=1;
				}
			}

			int dp = 0, de = 0;
			for (TSpan ts: bp.keySet()){
				if (text.getSentenceIdByPos(ts.startIdx) != i) continue;
				if (bp.get(ts) > 1)
					dp ++;
			}
			for (TSpan ts: be.keySet()){
				if (text.getSentenceIdByPos(ts.startIdx) != i) continue;
				if (be.get(ts) > 1)
					de ++;
			}
			if (dp > DP){
				DP = dp;
				System.err.println("[DP]"+DP+" "+i+"@"+doc.fileId);
			}
			if (de > DE){
				DE = de;
				System.err.println("[DE]"+DE+" "+i+"@"+doc.fileId);
			}
			System.err.println("[GRAPHSTAT]\t"+numPar+"\t"+ numBinding+"\t"+numChild +"\t"+numProtein+"\t"+numArgs+"\t"+numEvents);
		}
		HashSet<TSpan> eventTSpans = new HashSet<TSpan>();

		for (Event event: doc.events.values()){
			eventTSpans.add(event.tspan);
		}


		//		System.err.println("[GRAPHSTAT]"+shareArg+"\t"+doc.events.size()+"\t"+eventTSpans.size()+"\t"+doc.proteins.size()+"\t"+numEdge);
	}

	/**
	 * 
	 * @param e
	 * @param events
	 * @param visited : to avoid mutual reference
	 * @return
	 */
	public static boolean checkValid(Event e, HashSet<Event> events, Hashtable<Event, Boolean> visited){
		boolean valid = true;
		//		System.err.println("checking "+e);
		for (Event ee: events){
			if (e==ee)  return false;
		}
		//		System.err.println("checking if already checked "+e );
		for (Entry<Event, Boolean> entry: visited.entrySet() ){
			if (entry.getKey() == e){
				//				System.out.println("yes, already checked ");
				return entry.getValue();
			}
		}
		//		System.err.println("visited : " + events);
		events.add(e);
		boolean hasProteinAsTheme = false;
		if (e.args.size() == 0){
			valid = false;
		}
		else{
			ArrayList<Integer> toRemove = new ArrayList<Integer>();
			for (int i = 0; i < e.args.size(); i++){
				if (e.args.get(i) instanceof Event){
					boolean flag = checkValid((Event)e.args.get(i), events, visited);
					if (!flag)
						toRemove.add(i);
				}
				else if (e.args.get(i) instanceof Protein){
					if (e.types.get(i)== ArgType.Theme){
						hasProteinAsTheme = true;
					}
					if (e.types.get(i) == ArgType.Cause && e.eventType!=null && e.eventType == EventType.Gene_expression){
						toRemove.add(i);
					}
				}
			}
			for (int i = 0; i < toRemove.size(); i++){
				e.args.remove(toRemove.get(toRemove.size()-1-i));
				e.types.remove(toRemove.get(toRemove.size()-1-i));
			}
			if (e.args.size() > 0)
				valid = true;

		}
		visited.put(e, valid);
		//		visited.remove(e);
		//		events.remove(e);
		return valid;
	}


	public void learn(){
		Count sum = new Count();
		this.eventCauseEvent.clear();
		this.eventCauseProtein.clear();
		this.eventThemeEvent.clear();
		this.eventThemeProtein.clear();
		String path  =BioNLPSettings.getValue("dataPath");
		//			BioNLPSettings.parsePath = BioNLPSettings.trainParsePath;
		for (String filename :  new File(path).list()){
			//						String filename = "9825820.txt";
			if (filename.endsWith(".txt")){
				//					if (r.nextDouble()> 0.1)
				//						continue;
				String filePrefix = filename.replace(".txt", "");
				System.out.println(filename+ " to be processed");
				sum.add(processDocument(path, filePrefix));
			}
		}
		System.out.println(sum);

		// *** uncomment the following ***
		savePatterns();
	}

	/**
	 * for learning
	 * @param path
	 * @param filePrefix
	 * @return
	 */
	public /*static */Count processDocument(String path, String filePrefix){
		Count c = new Count();

		// 1. read the parses, both constituent and dependency
		// FIXME: consider separate class dealing with that
		BioNLPDocument doc = new BioNLPDocument(path, filePrefix);
		if (doc.tspans.size() == 0){
			System.out.println(filePrefix + " skipped");
			return c;
		}

		if (!doc.valid)
			return c;
		// find patterns
		// 2.	for each event and each arg, find the dep path

		AbstractText text = (AbstractText)doc.text;
		for (Event e: doc.events.values()){
			//			if (e.eventType== EventType.Binding){
			//				bindingDictionary.add(e.tspan.text.toLowerCase());
			//			}
			//			System.err.println("[EDGEDETECTION]"+filePrefix + " [EVENT] "+ e);

			int eventSentIdx = text.getSentenceIdByPos(e.tspan.startIdx);
			for (int i = 0; i < e.args.size(); i++){
				int argSentIdx = text.getSentenceIdByPos(e.args.get(i).tspan.startIdx);
				if (eventSentIdx != argSentIdx){
					System.out.println("processDocument@EventDepTemplate : skipping pair (not in the same sentence) for {"+e+" @@ "+e.args.get(i)+"}");
				}
				else{

					int offset = text.sentStartPos[eventSentIdx];


					DependencyPath pp = doc.parses[eventSentIdx].getShortestPath(e.tspan.startIdx - offset, e.tspan.endIdx - offset, 
							e.args.get(i).tspan.startIdx - offset, e.args.get(i).tspan.endIdx - offset);

					System.err.println("[EDGE_LEARN]"+filePrefix + "Event: "+e+", Argument: "+e.args.get(i));
					System.err.println("[EDGE_LEARN]"+filePrefix + "Text: "+ text.lines[eventSentIdx]);
					//					System.out.println("Found paths : "+list);

					// 2b. put each into the corresponding category
					if (pp!=null){
						String p = pp.getFeatureString();
						System.err.println("[EDGE_LEARN]"+"[DEPPATH]"+pp);
						//					if(i >1)	return c ;

						if (e.args.get(i) instanceof Event){
							// add tspan to parent trigger set
							//							parentDictionary.add(e.tspan.text.toLowerCase());
							Parse parse  =doc.parses[eventSentIdx];
							int triggerWord = parse.getWord(e.tspan.startIdx - offset);
							int argWord =parse.getWord(e.args.get(i).tspan.startIdx - offset);
							int triggerHead = parse.getHead(triggerWord);
							int argHead = parse.getHead(argWord);
							if (triggerWord ==-1 || argWord == -1 || triggerHead == -1 || argHead == -1){
								System.err.println("[EDGE_LEARN] NOT FOUND "+ triggerWord +" "+argWord  +" "+ triggerHead  +" "+ argHead );
							}
							else{
								System.err.println("[EDGE_LEARN]"+filePrefix+" triggerWord="+ parse.words[triggerWord]
								                                                                          +", triggerhead="+parse.words[triggerHead]+", argWord="+parse.words[argWord]+", argHead="+parse.words[argHead]);
								DependencyPath headDepPath = doc.parses[eventSentIdx].getShortestPath(triggerHead, argHead);
								if (headDepPath != null){
									System.err.println("[EDGE_LEARN]"+filePrefix + " [HEAD_DEP] ArgType=" + e.types.get(i)+"  DP="+headDepPath.getFeatureString() +" non-head-dp = "+ p);
									//									p = "H"+headDepPath.getFeatureString();
								}
								else{
									System.err.println("[EDGE_LEARN]"+filePrefix + " [HEAD_DEP] ArgType=" + e.types.get(i)+"  DP=null");
								}

							}


							//						for (DependencyPath p: list){
							if (e.types.get(i) == ArgType.Cause){
								if (!eventCauseEvent.containsKey(p)){
									eventCauseEvent.put(p, 0);
								}
								eventCauseEvent.put(p, eventCauseEvent.get(p)+1);
							}
							else if (e.types.get(i) == ArgType.Theme){
								if (!eventThemeEvent.containsKey(p)){
									eventThemeEvent.put(p, 0);
								}
								eventThemeEvent.put(p, eventThemeEvent.get(p)+1);
							}
							//						}
						}
						else if (e.args.get(i) instanceof Protein){
							Parse parse  =doc.parses[eventSentIdx];
							int triggerWord = parse.getWord(e.tspan.startIdx - offset);
							int argWord =parse.getWord(e.args.get(i).tspan.startIdx - offset);
							int triggerHead = parse.getHead(triggerWord);
							int argHead = parse.getHead(argWord);
							if (triggerWord ==-1 || argWord == -1 || triggerHead == -1 || argHead == -1){
								System.err.println("[EDGE_LEARN] NOT FOUND "+ triggerWord +" "+argWord  +" "+ triggerHead  +" "+ argHead );
							}
							else{
								System.err.println("[EDGE_LEARN]"+filePrefix+" triggerWord="+ parse.words[triggerWord]
								                                                                          +", triggerhead="+parse.words[triggerHead]+", argWord="+parse.words[argWord]+", argHead="+parse.words[argHead]);
								DependencyPath headDepPath = doc.parses[eventSentIdx].getShortestPath(triggerHead, argHead);
								if (headDepPath != null){
									System.err.println("[EDGE_LEARN]"+filePrefix + " [HEAD_DEP] ArgType=" + e.types.get(i)+"  DP="+headDepPath.getFeatureString() +" non-head-dp = "+ p);
									p = "H"+headDepPath.getFeatureString();
								}
								else{
									System.err.println("[EDGE_LEARN]"+filePrefix + " [HEAD_DEP] ArgType=" + e.types.get(i)+"  DP=null");
								}

							}
							if (e.types.get(i).match(ArgType.Theme) ){
								//							for (DependencyPath p: list){
								if (!eventThemeProtein.containsKey(p)){
									eventThemeProtein.put(p, 0);
								}
								eventThemeProtein.put(p, eventThemeProtein.get(p)+1);
								//							}
							}
							else if (e.types.get(i) == ArgType.Cause){
								//							for (DependencyPath p: list){
								if (!eventCauseProtein.containsKey(p)){
									eventCauseProtein.put(p, 0);
								}
								eventCauseProtein.put(p, eventCauseProtein.get(p)+1);
								//							}
							}
							else {
								// skip this
							}
						}
						else{
							System.out.println("processDocument@EventDepTemplate : argument is not event or protein , "+e.args.get(i)+"  ;; "+e.types.get(i));
						}
					}
					else{
						System.err.println("[EDGE_LEARN] cant find dep path" );
					}
				}
			}
		}
		return c;
	}

	public TriggerDetection td = null;

	public/* static*/ Hashtable<String, Integer> eventThemeEvent = new Hashtable<String, Integer>(),
	eventCauseEvent = new Hashtable<String, Integer>(),
	eventThemeProtein = new Hashtable<String, Integer>(),
	eventCauseProtein = new Hashtable<String, Integer>();


	// for debug use, not the same as the ones in <code>BioNLPSettings</code>
	public static int BP = 0, BE = 0;
	public static int DP = 0, DE = 0;
}

